Processingでレイアウトエディタを作ろう

前回に続いて、Processing Advent Calendar 2011の12/15の記事を書きました。

今回は、レイアウトエディタもどきを作ってみたいと思います。

でも、いったい何をレイアウト(配置)するのか?
ひとことで言うなら、「描画関数をレイアウト」します。

LayoutEditorサンプルのページへ

ソースファイルは2つ。
描画関数を抽出したり、描画リストを管理する LayoutManager.pde と、
マウス位置にあわせて描画関数を描画リストに登録する LayoutEditor.pde です。

まず、setupの中で LayoutManager というクラスを初期化しています。

// 初期化
void setup() {
  size(512, 512);

  // 描画関数をレイアウトマネージャに登録
  layout = new LayoutManager(this, "draw_");

  setupGUI();
}

このLayoutManager クラスは、引数として渡された "draw_" という文字列で始まる関数を this (PApplet) の中から見つけてきて記録しています。

// 特定の名前で始まる描画関数を抽出
LayoutManager(Object classObj, String methodPrefix) {
  this.classObj = classObj;
  this.methodPrefix = methodPrefix;
  this.methods = new HashMap<String, Method>();
  this.items = new ArrayList<Item>();

  // 引数で渡されたクラス内で特定名で直接実装された関数を記録
  Method decMethods[] = classObj.getClass().getDeclaredMethods();
  for(Method m: decMethods) {
    if(m.getName().indexOf(methodPrefix)==0) {
      println("LayoutManager: add method : " + m.getName());
      methods.put(m.getName(), m);
    }
  }
}

関数を名前で探すためには、クラス情報にアクセスする必要があります。
そのためにJava のリフレクションという機能を使ってます。

getDeclaredMethods() は、直接宣言している全てのメソッドを返す関数です。つまり、親クラスのメンバは無視してるわけですね。まぁリフレクションの乱用は禁物だと思いますが、なかなか面白いです。[1]

記録した描画関数のテーブル(HashMap)は、LayoutEditorの描画関数選択や描画リスト追加で使用しています。

なお、リフレクションを使うには、最初に以下のような import をしておく必要があります。

import java.lang.reflect.*;
  • [1]ちなみに、名前で抽出というのはどうもなぁ、と思ったときには、関数の前に「@〜」と独自の印を付けられるアノテーションという機能を組み合わせるとよいでしょう。ただ、Processingの一見グローバルな位置にあるコードは、実際はProcessingのアプリクラス内部のコードになります。独自アノテーションを定義するには、ライブラリ化やEClipse利用など、工夫が必要かもしれません。

さて、ここまでで描画関数全種類の記録はできました。あとは現在選択している描画関数を、
クリックした位置にあわせて LayoutManager の描画リストへ追加するだけです。

layout.add(currentMethod, mouseX, mouseY);

この currentMethod は関数の名前を持つString型の変数です。

今回は controlP5 というライブラリを使って、ドロップダウンリストやボタンを作っています。
GUIについては、サンプルコード、LayoutEditor.pde の setupGUI や controlEvent 関数を見てみてください。

リフレクションで取得した関数の実行方法は、LayoutManager の draw 関数内にあります。

// 登録した描画コマンドを発行
void draw() {
  for(Item it: items) {
    pushMatrix();
    applyMatrix(it.matrix);        // 位置などを設定
    try {
      it.method.invoke(classObj);  // 登録してある描画関数の呼び出し
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    popMatrix();
  }
}

it.method.invoke(classObj); が関数を実行している箇所です。
try〜catchの例外処理で挟まないとエラーになるのでご注意を。

今回はよりクレイジーな雰囲気を重視して(?)、リフレクション機能を使って生の関数を登録するようにしていますが、引数やメンバの値をパラメータ設定して渡したり、クラスを使ったりするのもよいと思います。

たとえば、GUI部品レイアウトエディターとか作れそうです。

描画位置については、今回は位置だけなので applyMatrix ではなく translate で十分なのですが、
回転やスケーリングもすぐに対応可能なように Matrix にしてあります。

そうそう、書き忘れていましたが、アプレット上では「Save」ボタンが使用できません。
ローカルで「Save」すると、LayoutEditor で追加した描画リストをProcessingのコードとして出力できます。

Processingはソースコードを書いて簡単に絵的な表現ができるのが楽しい言語ですが、
逆に絵的な入力からソースコードを出すこともできる、と。

最後にオマケとして3Dバージョンも作ってみましたので、参考までにどうぞ。
こちらは controlP5 のP3Dでのテキスト描画に問題があったため、
かわりに SpringGUI という GUI ライブラリを使っていますが、やっていることは同じです。

LayoutEditor3Dサンプルのページへ

Utility.pde は前回の Unproject.pde 内から必要な関数を持ってきただけです。
LayoutEditor3D.pde は LayoutEditor.pde と Unproject.pde をあわせた感じで、
2Dを3D対応にして、カメラ回転や移動ができるようにしました。
LayoutManager.pde の中身は同じです。

えーと、今回はこんなところです。

昨今はソースコードと素材データだけでモノを作る作り方だけではなく、エディターと連携した作り方も増えてきています(といいつつ、実際は昔からそうですけど)。そういったツール作りの参考になればと思います。

工夫すればUnityのような「動かしながらの調整」ができるリアルタイムエディタも作れるかもしれませんね。

# 本当は3Dの方を先に作ったのですが、余計なものが多いので2Dバージョンを作ったという。



Trackback(0)