前回の続き。これで完結。
Clutter: a beginner’s tutorial: 原文
プログラムの一部が間違っていたので修正しておいた 。
大きさの変更やオブジェクトの選択
ここまでで、Clutter を使ってオブジェクトを回転させるのは簡単であることが分かりました – それではもう少し先に進んで、四角形の大きさも変えてみることにします。
ただ Clutter はとても賢いシステムなのに、単純な数値計算なんかはプログラマに実装させてきました。正直に言えば、この点は本チュートリアルで少々思慮に欠けていました。その点を補うため、前言を撤回して、どんな場合でもちゃんと結果の出せる実装を記述したC言語のちいさなヘッダを導入することにします。
このファイルをダウンロードし
mathhacks.h という名前で作業フォルダに保存して下さい。そして、今まで記述してきたプログラム・ファイルの中から #include ディレクティブでインクルードするよう追加してみて下さい。
そのヘッダ・ファイルを使えば、2つの値の間を補間する数値計算処理を利用できるようになります。これは、四角形を「滑らかに」大きくしたり小さくすることが、0〜1にある値 (倍率) を渡すだけで簡単に実現できると言うことです。補間した値は何か変数に格納しておく必要があるので、次に示すコードを “gdouble rotation” がある行の下に挿入して下さい:
gdouble scale = 0;
その変数を使い、on_timeline_new_frame() 関数に四角形の大きさを変更するコードを追加します – 次のコードを四角形を回転させるコードの後に挿入して下さい:
scale += 0.01;
if (scale > 1.0) scale = 0;
float scale_amount = smooth_step2(1.0, 2.0, scale);
clutter_actor_set_scale(rect1, scale_amount, scale_amount);
clutter_actor_set_scale(rect2, scale_amount, scale_amount);
clutter_actor_set_scale(rect3, scale_amount, scale_amount);
clutter_actor_set_scale(rect4, scale_amount, scale_amount);
clutter_actor_set_scale(rect5, scale_amount, scale_amount);
clutter_actor_set_scale(rect6, scale_amount, scale_amount);
smooth_step2() は、今回 mathhacks.h の中で定義した小さな即席の関数の1つで、XとYの間をZの値に応じて補間してくれます (Zは0〜1の値です)。このサンプル・プログラムの場合、倍率を表す “scale” にあわせて1.0と2.0との間で補間した値を返します。
この smooth_step2() 関数は、倍率を表す “scale” が0.5未満であれば1.0から2.0の間を補間し、”sclae” が0.5以上ならば2.0から1.0の間を補間します。要するに、四角形の大きさが元のサイズの2倍になったら、今度は倍率を小さくしていくと言うことです。それにより大きくなったり小さくなったりのアニメーションが滑らかに表現できます。同様に、関数名に “2” がついていない版の smooth_step() も用意されており、こちらは四角形を大きくするだけです (訳注: 大きくなった後は四角形を最初から描画します)。
ここまでのサンプル・プログラムをコンパイルして実行すると、四角形が回転しながら大きくなったり小さくなったりするアニメーションをみることができます。どうです? 簡単でしょう – では、どんどん先に進んでみることにしましょう。次はオブジェクトの選択 (Picking) です。
アニメーションの中にはたくさんのオブジェクトが表示されていますが、その中にある1つのオブジェクトをクリックされたことをどのように検出すればよいのでしょうか? 四角形の大きさが変化せず回転もしていないのであれば「クリックした点が四角形の境界線より内側かどうか?」で判定する方法が簡単そうですが、このサンプル・プログラムの場合、オブジェクトを変化させる効果によりそれをとても難しくしています。幸いにも Moblin ではそれをたやすく実現できるようになっています: 画面をクリックしたら、Moblin に任意の関数を呼び出して実行するよう要求できるようになっており、その関数の中でどのアクターがクリックされたのかが分かるようになっています – それでは、まず次のコードを main() の中で最後に create_rect() 関数を呼び出している行の後にそのまま挿入して下さい:
g_signal_connect(stage, "button-press-event", G_CALLBACK(on_stage_button_press), NULL);
ここで “button-press-event” というシグナルに on_stage_button_press() という新しい関数を関連づけます。タイムフレームを実現した時と同様に、この関数は特別な引数を受け取れるようになっているので、必要に応じてコピー&ペーストを行います。新しい関数で処理することは
- クリックした点の座標 (X,Y) を Clutter に問い合わせる
- この座標を使ってどのアクターがクリックされたかを判定する
- 当該アクターを隠す
実際のコードは次のようになります – この関数は main() 関数の前で記述すること:
void on_stage_button_press(ClutterStage *stage, ClutterEvent *event, gpointer data) {
// 画面のどこをクリックしたかを検査する
gfloat x = 0;
gfloat y = 0;
clutter_event_get_coords(event, &x, &y);
// どのアクターがクリックしたのか調査する
ClutterActor* clicked = clutter_stage_get_actor_at_pos(CLUTTER_STAGE(stage), CLUTTER_PICK_ALL, x, y);
// クリックしたオブジェクトがステージの場合は無視する
if (clicked == CLUTTER_ACTOR(stage)) return;
// クリックした四角形を表示しないようにする
clutter_actor_hide(clicked);
}
ここまで実装してきたサンプル・プログラムをコンパイルして実行すると、前回のサンプルと同じように四角形が回転しながら大きくなったり小さくなりますが、その中の四角形をクリックして消してしまうことができます。Clutter の Depth-Sort アルゴリズムでは、四角形をステージに追加した順番に並び替えるようになっているため、ある四角形と他の四角形とが交差して重なっている部分をクリックすると、一番上にある四角形がクリックされたと認識されます。
(Clutter を使えば、オブジェクトを回転させながら大きさを変えることができるということが分かりました。まさに「OpenGL 万歳!」といったところでしょう。)
スコアを記録する
タイムラインを使えば自由にオブジェクトのアニメーションを実現できますが、荒削りの感も否めません – アニメーションに関連する全ての処理を1つの関数にまとめてしまっているので、複雑なアニメーションを実現するには少々厄介です。お分かりかと思いますが、Clutter はスコア付きで面倒をみてくれます。並列でも直列でも実行できるタイムラインのコレクションが用意されているので、必要に応じて使い分けできます。この動きを確認するために、まず最初に現在の on_timeline_new_frame() 関数を2つの部分に分割します – 1つ目は四角形を回転する処理で、もう1つは四角形の大きさを変更する処理です。実際には次のような2つの関数を用意することになります:
void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, gpointer data) {
rotation += 0.3;
clutter_actor_set_rotation(rect1, CLUTTER_Z_AXIS, rotation * 5, 0, 0, 0);
clutter_actor_set_rotation(rect2, CLUTTER_Z_AXIS, rotation * 4, 0, 0, 0);
clutter_actor_set_rotation(rect3, CLUTTER_Z_AXIS, rotation * 3, 0, 0, 0);
clutter_actor_set_rotation(rect4, CLUTTER_Z_AXIS, rotation * 2, 0, 0, 0);
clutter_actor_set_rotation(rect5, CLUTTER_Z_AXIS, rotation, 0, 0, 0);
clutter_actor_set_rotation(rect6, CLUTTER_Z_AXIS, rotation * 0.5, 0, 0, 0);
}
void on_timeline_scale_new_frame(ClutterTimeline *timeline, gint frame_num, gpointer data) {
scale += 0.01;
if (scale > 1.0) scale = 0;
float scale_amount = smooth_step2(1.0, 2.0, scale);
clutter_actor_set_scale(rect1, scale_amount, scale_amount);
clutter_actor_set_scale(rect2, scale_amount, scale_amount);
clutter_actor_set_scale(rect3, scale_amount, scale_amount);
clutter_actor_set_scale(rect4, scale_amount, scale_amount);
clutter_actor_set_scale(rect5, scale_amount, scale_amount);
clutter_actor_set_scale(rect6, scale_amount, scale_amount);
}
ここでは、まだ新しいコードは追加していません – 既存の関数を2つに分割しただけです。これで、まずスコアを新規に生成し、それを使って以前のアニメーションの動作を再構築し、それから2個のタイムラインをスコアに追加して並列で動作させる準備が整いました。ここでは clutter_timeline_set_loop() 関数は使いません。なぜならタイムラインをループさせたくないからです – スコアにループして貰いたいのです。Clutter では関数名の付け方を見れば、その関数で何を処理したいのかが分かるようになっていますが、clutter_score_set_loop() という関数の場合、タイムラインをそれぞれループさせるのではなく、スコア全体をループさせていることがわかると思います。
タイムラインを使った場合と同様に、スコアも直接破棄する必要があります – すなわち、ステージが破棄されてもスコアは自動的に破棄されません。そのため main() 関数の最後の部分を次のように変更して下さい:
// 新規にスコアを1個生成しループにセットする
ClutterScore *score = clutter_score_new();
clutter_score_set_loop(score, TRUE);
// 新規にタイムラインを1個生成し、その所要時間を1/2秒にする
ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), NULL);
// タイムラインをスコアに追加する
clutter_score_append(score, NULL, timeline_rotation);
// 別のタイムラインを生成し、これも同じように所要時間を1/2秒にする
ClutterTimeline *timeline_scale = clutter_timeline_new(500);
g_signal_connect(timeline_scale, "new-frame", G_CALLBACK(on_timeline_scale_new_frame), NULL);
// 同様に、このタイムラインをスコアに追加する
clutter_score_append(score, NULL, timeline_scale);
// スコアを開始する
clutter_score_start(score);
clutter_actor_show(stage);
clutter_main();
// 使用済みのメモリを解放する
g_object_unref(timeline_rotation);
g_object_unref(timeline_scale);
g_object_unref(score);
return EXIT_SUCCESS;
ここまでのプログラムをコンパイルして実行すると、以前の実行結果から特に変化が無いことが分かるでしょう: 以前と同じように四角形が回転しながら大きくなったり小さくなります。その理由は複数のタイムラインをスコアに追加して、それらを並列に実行するよう Clutter に依頼したからです – on_timeline_rotation_new_frame() 関数が一度だけ呼び出されると、その次に on_timeline_scale_new_frame() も一度だけ呼び出されます。
ここで Clutter にやらせたいことは、on_timeline_scale_new_frame() 関数の処理に切り替わる前に、on_timeline_rotation_new_frame() 関数を 500ms 間実行させたたいということです。そのためには、clutter_score_append() 関数の呼び出しを変更する必要があります – この関数の2番目の引数を NULL にします (これは「親に相当するタイムラインは持たない – 可能な時にそのタイムラインを実行させる」という意味になります)。2つ目の clutter_score_append() 関数の呼び出しを次のように変更してみて下さい:
clutter_score_append(score, timeline_rotation, timeline_scale);
ここまでで、四角形の大きさを変更するタイムラインをスコアに追加し、そのスコアを四角形を回転させるタイムラインの親にしました。すなわち、これで2つあるタイムラインは連続して実行されるようになりました: 最初に四角形を回転させて、次に四角形の大きさを変更します。このコードをコンパイルして実行するとわかりますが、これが実際にやりたかったことなのです – 四角形が少しだけ回転すると、回転を停止してちょっとだけ大きくなります。それから再び停止して回転していきます。この動作を繰り返すわけです。
もっと色の付いた四角形をたくさん描画する
既に説明したように、Clutter ではいろいろな種類のアクターを利用できますが、それらのほとんどを同じ方法で扱うことができます (ここがすごいところなのですが)。 例えば単に四角形を描画するのではなく、写真を回転させたり大きさを変えたいのであれば、create_rect() 関数の中にある1行を変更するだけです – すなわち、色のついた四角形の代わりに用意した画像を使用します。まず、この関数の先頭の行を次の行で置き換えます:
ClutterActor *rect = clutter_texture_new_from_file("media/card_1.png", NULL);
他に変更することはありません。ここでプログラムをコンパイルして実行させると、前のサンプルのように四角形ではなく指定した画像が描画されるはずです。
(Clutter は PNG だとか JPEG 形式の画像を読み込んで、他の種類のアクター同様に扱うことができます。)
さらに Clutter は、文字列のような、もっと複雑なオブジェクトも扱うことができます – 先ほど変更した行を次の行で置き換えてみて下さい。どうです?簡単でしょう:
ClutterActor *rect = clutter_text_new_full("Sans 24", "Hello, world", &col);
そうです、これだけで今までの四角形が文字列から構成されるメッセージに変わります (回転したり大きさが変わるのは前と同じですが)。clutter_text_new_full() 関数に渡す最初の引数は使用するフォントの名前とサイズです。2番目の引数は実際に表示する文字列で、3番目の引数はその文字列を描画する際の色になります。
但し、ここで描画している文字列はそれほど複雑なものではありませんが – 実際にはキーボードから入力した文字列だとか、改行文字を含む長い文章も同じように扱えます。このようなことを試してみるには2点ほど変更する必要があります。 まず最初に、次の行を create_rect() 関数にある clutter_text_new_full() 関数の下に追加します:
clutter_text_set_editable(CLUTTER_TEXT(rect), TRUE);
次に on_stage_button_press() 関数を変更します。今回は、クリックしたアクターを消すのではなく、ユーザにキーボードのフォーカスを与えて何か文字を入力できるようにします。この関数の最後を次のように変更します:
ClutterActor* clicked = clutter_stage_get_actor_at_pos(CLUTTER_STAGE(stage), CLUTTER_PICK_ALL, x, y);
if (clicked == CLUTTER_ACTOR(stage)) return;
clutter_actor_grab_key_focus(clicked);
(Clutter では面倒な手続きなしで文字列を扱うことができます。注意点: 画面の vsync を有効にした方がもっと綺麗な結果になるでしょう。)
最後に紹介するテクニック
本チュートリアルを切り上げる前に、試してもらいたいことが3つほどあります。最初に、サンプル・プログラムのウィンドウ・タイトルである “./clutterapp” ですが、この表記をそろそろ捨てさってしまってもよい時期でしょう。次のコードを main() 関数の clutter_actor_new() を呼び出す直前に追加してみて下さい:
clutter_stage_set_title(CLUTTER_STAGE(stage), "Spinny boxes ahoy!");
次に、Clutter が提供している全てのアクターは自分に関連づけることが可能なイベント (シグナル) を持ちます – マウスをクリックした、マウスを移動した、キーを押下した などなど。さらに、これらのイベントは特定のアクションに関連づけることが可能になっています。ここで重要なポイントは、アクターなるオブジェクトを任意のイベントに反応するよう指定するということです。すなわち、アクターは自分が発行することになるイベントを持つよう Clutter に指示するできるということです。Clutter の大部分のオブジェクトはイベントに反応しません。なぜなら、イベントに反応するのは CPU の無駄遣いだからです。そのため、利用したいイベントを有効にて下さい。そうしないとなぜ動作しないのかあれこれ考えることになりますよ!
イベントに反応する四角形を生成する場合、次に示すようなことをします:
ClutterActor *rect = clutter_rectangle_new_with_color(&col);
clutter_actor_set_size(rect, 256, 128);
clutter_actor_set_position(rect, 128, 128);
clutter_actor_set_reactive (rect, TRUE);
g_signal_connect (rect, "button-press-event", G_CALLBACK (on_rect_button_press), NULL);
ご覧のとおり、ここでは on_rect_button_press() という関数をコールバックとして四角形に関連づけています。この関連づけは最初に紹介した on_stage_button_press() 関数の時と全く同じです。但し、今回は (ご想像のとおり) ClutterActor 型のオブジェクトを第1引数として渡して関数の中でアクセスできるようにしています。このサンプル・プログラムでは四角形をクリックするとその色を変更することにします – それでは、次のコールバック関数を追加して下さい:
gboolean on_rect_button_press(ClutterActor *actor, ClutterEvent *event, gpointer data) {
ClutterColor newcol = { rand() % 256, rand() % 256, rand() % 256, 255 };
clutter_rectangle_set_color(CLUTTER_RECTANGLE(actor), &newcol);
return TRUE;
}
ステージの上で回転している四角形をクリックすると、Clutter はそのアクターをコールバック関数の第1引数に格納してくれます。いろいろな四角形に同じコールバック関数を関連づけ、その中からクリックした四角形だけを見つけてくれるというわけです。もし、この手法を採用せずに、ステージがクリックされた時にオブジェクトを直接判定するような処理を行うのであれば、CLUTTER_PICK_ALL を CLUTTER_PICK_REACTIVE に変更して、四角形だけを検出するように設定してみて下さい。
最後に、本チュートリアルではアニメーションの技法について大部分を割愛してきました。なぜなら、その説明は少々一筋縄にはいかないものであり、初心者向きのチュートリアルとしてはその範疇を超えているからです。とはいえ、Clutter でアニメーションを作成するのが本当に簡単であることについてさえも言及していないのあれば、それはそれで筆者の怠慢と言えますが。そして、clutter_actor_animate() という魔法の関数がやってくれることを一言で表現すると、アニメーションに関する複雑な処理を全てラッピングして隠してくれる上に、面白そうな好奇心を抱かしてくれるもの、と言えます。
このサンプル・プログラムを実行する前に、既存のコードを少し整理しておきましょう – 例えば、タイムラインは複数個ありますが、実際はそんなに必要ありません。整理したプログラムは次のようになります:
#include <clutter/clutter.h>
#include <stdlib.h>
// 6個のアクターとそれを動かすステージ
ClutterActor *stage = NULL;
ClutterActor *rect1 = NULL;
ClutterActor *rect2 = NULL;
ClutterActor *rect3 = NULL;
ClutterActor *rect4 = NULL;
ClutterActor *rect5 = NULL;
ClutterActor *rect6 = NULL;
// この関数で色のついた四角形を生成する
ClutterActor *create_rect(ClutterColor col) {
ClutterActor *rect = clutter_rectangle_new_with_color(&col);
clutter_actor_set_size(rect, 256, 128);
clutter_actor_set_position(rect, 256, 256);
clutter_actor_set_anchor_point(rect, 128, 64);
clutter_container_add_actor (CLUTTER_CONTAINER (stage), rect);
clutter_actor_show (rect);
return rect;
}
// この関数については後で実装する予定...
void on_stage_button_press (ClutterStage *stage, ClutterEvent *event, gpointer data) {
// ここに処理を書く
}
int main(int argc, char *argv[]) {
clutter_init (&argc, &argv);
ClutterColor stage_color = { 0, 0, 0, 255 };
stage = clutter_stage_get_default();
clutter_actor_set_size (stage, 512, 512);
clutter_stage_set_color (CLUTTER_STAGE(stage), &stage_color);
ClutterColor red = { 255, 0, 0, 128 };
ClutterColor green = { 0, 255, 0, 128 };
ClutterColor blue = { 0, 0, 255, 128 };
ClutterColor yellow = { 255, 255, 0, 128 };
ClutterColor cyan = { 0, 255, 255, 128 };
ClutterColor purple = { 255, 0, 255, 128 };
rect1 = create_rect(red);
rect2 = create_rect(green);
rect3 = create_rect(blue);
rect4 = create_rect(yellow);
rect5 = create_rect(cyan);
rect6 = create_rect(purple);
g_signal_connect (stage, "button-press-event", G_CALLBACK (on_stage_button_press), NULL);
clutter_stage_set_title(CLUTTER_STAGE(stage), "Spinny boxes ahoy!");
clutter_actor_show (stage);
clutter_main ();
return EXIT_SUCCESS;
}
このサンプル・プログラムをコンパイルして実行すると、6個の四角形が、まるで命を吹き込んでもらいたいのを待っているかのように、全て真ん中にじっとした状態で表示されるはずです。ここで実際にやりたいことは、clutter_actor_animate() 関数を使い、これらの四角形を回転させながらステージの別の場所へ飛ばすということです。この clutter_actor_animate() 関数には驚くべき機能がいくつか搭載されていますが、その中から3つほど説明します:
- アニメーションの種類を簡単に変更できる
- 1回だけの呼び出して、いろいろなオブジェクトのアニメーションを作成できる
- 前のアニメーションが終了する前に clutter_actor_animate() 関数を呼び出すと、Clutter はそれを「賢くエレガントに」処理してくれる
サンプル・プログラムの “// ここに処理を書く” というところに、次のコードを記述して下さい:
move_rect(rect1);
move_rect(rect2);
move_rect(rect3);
move_rect(rect4);
move_rect(rect5);
move_rect(rect6);
まだ move_rect() なる関数は定義していませんが、生成した四角形を引数にして何回か呼び出すものだと推測することができます。この move_rect() 関数は、1個の四角形をランダムに回転させながらステージの上のランダムな場所に移動します – で、ここで重要なポイントは、乱数として得られたランダムな値を整数値ではなく実数値として指定しなければならないと言うことです。もしここでそのまま整数値を使ってしまうと、ほぼ確実にプログラムはクラッシュします。そのため、float 型にキャストしてあげて下さい (それだけです)。
move_rect() 関数の実装は次のようになります:
void move_rect(ClutterActor *rect) {
clutter_actor_animate (rect,
CLUTTER_LINEAR,
1000,
"x", (float)(rand() % 512),
"y", (float)(rand() % 512),
"rotation-angle-z", (float)(rand() % 360),
NULL);
}
実際には、ランダムにクリックすることで clutter_actor_animate() 関数を別々に複数回呼び出すことになります。それでは詳細を見ていくことにします:
- CLUTTER_LINEAR がアニメーションの種類で、アニメーション中は同じスピードでオブジェクトを移動します
- 1000 はアニメーションを実行する時間 (単位はミリ秒)です
- “x” がある行はアクターを移動する先のX座標を計算しており、”x” はアクターのプロパティ名で文字列として指定します
- “rotation-angle-z” もアクターのプロパティ名で、これでアクターを回転角を計算します
- アクターのプロパティを全て指定し終わったら、最後に NULL を渡します
このプログラムをコンパイルして実行し、ステージ上のどこかをクリックすると四角形が動き出します。でも、それだけでは少々退屈ではありませんか? clutter_actor_animate() 関数で指定した CLUTTER_LINEAR を CLUTTER_EASE_IN_OUT_BACK に変更し、もう一度コンパイルして実行してみて下さい。その結果はまだ話していませんが、きっと気に入ると思います…
(ステージの周りを四角形が優雅に飛んでいきます – これは画像や文字列を使っても簡単に実現できます。あるいは他の種類のアクターでも実現んできるでしょう)
とりあえず…以上です
本チュートリアルで紹介した内容は Clutter の入門書なる部分のみを対象していますが、これを執筆した数時間でいろいろと多岐にわたり充実した内容に仕上がったと思います。ここで、Canonical 社が少なくとも Ubuntu Netbook Remix (UNR) の GUI として Clutter を採用した理由は明白でしょう。Clutter は、賢く魅力的なユーザインタフェースを本当に素早く簡単に作成する手法を提供しています。実際のところ、UNR のユーザ・インタフェースを手にとって、それが本当かどうかを確認しできますし、その際はこう思うかもしれません – 「たぶん、私にも作れるかもしれない」。そう、あなたにもできます。