記事の目次

    大分ご無沙汰になってしまいましたが、お待たせしました。今回から2回にわけて、実際にプログラムを書く際の考え方と参考資料の調べ方、そして構文の書き方について、MAXScriptの具体例を下に解説していきます。

    プログラムの書き方自体が分からない

    前回は、MAXScript の簡単な文法の解説をしました。でも、文法を覚えても実際に作りたいプログラムが書けるかと言ったらそんなことはありません。大抵の人はエディタを目の前にして「はて、どこから手をつけよう......?」と、途方に暮れてしまうことでしょう。そして1時間ほどボーッとした挙句、作業の自動化を諦めて手作業で全てを行うという、いつも通りの仕事に戻ってしまいます。

    この原因は何でしょう? それはズバり、"プログラムの書き方を知らない"からです。私たちは普段から日本語を使ってコミュニケーションを図っていますが、いざ誰かを感動させる素晴らしい小説を書こうとしてもどうやってお話を作ればいいのか、その方法を知らないために形にすることができません。プログラミングにも同じことが言えるのです。

    いくら本職のプログラマーでも、プログラムを書くときに何の資料もなくプログラムを書くことはできません。スクリプトリファレンスと首っぴきになり、CGソフトを触って挙動を調べながら、あーでもない、こーでもないと試行錯誤して何とか形にするのは誰でも同じでしょう。プログラマーとそうでない人の違いは、これまでの経験から希望の機能を実現するためには何をすれば良いのかを知っていたり、そのための調べ方/ドキュメントの読み方を知っているというところにあります。そこで、今回と次回の2回をかけて、プログラムを書く時に私がどのように考え、ドキュメントを調べ、1つの形にしているのかという流れを紹介していきましょう。

    疑似言語でプログラムを書く

    プログラミングに慣れていない人はここからどうしたらいいのか迷ってしまうと思います。まず最初に必要なのは、自分がどんなプログラムを書きたいのか、そのためにはどのような機能が必要なのかをまとめることです。そこで、まずは疑似言語として日本語とプログラミング言語を混ぜた状態でコードを書いていきます。
    前回の最後に書いたBoxジオメトリを生成するプログラムをコードにするとこのようになります;

    
    
    	Boxを10×10個配置する
    
    

    「え!?」と思われるかもしれませんが、これもコードなのです。この疑似コードを実際のプログラムにしていきますね。MAXScript で実行できる機能に分解したのが以下の疑似コードです。どうでしょう? これならプログラムに慣れていなくても理解できるのではないでしょうか。

    
    
    	Boxを10×10個配置する (
    		Y方向に10回繰り返す (
    			X方向に10回繰り返す (
    				Boxを作る
    				Boxの位置を(x*10, y*10)にする
    				名前を "Box_x*10_y*10" にする
    			)
    		)
    	)
    
    

    .そして、MAXScript に翻訳したものは以下の通りでした;

    
    
    	for i=0 to 9 do (
    		for j=0 to 9 do (
    			x = i * 10
    			y = j * 10
    			b = Box pos:[x, y, 0] length:1 width:1 height:1
    			b.Name = "Box_" + (i as String) + "_" + (j as String)
    		)
    	)
    
    

    翻訳前と翻訳後を見比べてみてください。完全とは言いませんが、各行がほぼ一対一で対応しているのが判りますね。プログラムを書くというのは、このように作りたい機能を細かい要素ごとに分解して、それをプログラミング言語で表現できる形に変換する作業なのです。

    [[SplitPage]]

    関数を使う

    この、"Boxを10×10個配置する"というのはもうちょっと整理すれば機能として再利用できそうです。プログラムでは、こういったよく使う機能をひとまとめにすることができます。このようなひとまとめの機能を"関数"と言います。MAXScript では以下のように関数を定義します。

    
    
     fn funcName = (
     )
    
    

    Boxジオメトリを10×10個配置する関数は以下のようになります;

    
    
     fn CreateBox10x10 = (
       for i=0 to 9 do (
         for j=0 to 9 do (
           x = i * 10
           y = j * 10
           b = Box pos:[x, y, 0] length:1 width:1 height:1
           b.Name = "Box_" + (i as String) + "_" + (j as String)
         )
       )
     )
    
    

    ところが、このままでは"Boxを10×10個配置する" ということしかできません。10×20 個配置したい場合や、Z 方向にも配置したい場合には、 CreateBox10x20 という関数や CreateBoxZ のような関数を作らなければいけないのでしょうか? そんなことはありません。関数を"Boxをnx×ny×nz個配置する"という具合に、自由に指定できるようにするのです。

    そこで、nx, ny, nz を指定するために"引数"を使います。関数に引数を渡すことで、その場その場に応じて関数の動きを変えることができます。
    関数の作成については MAXScritpt リファレンスの"関数の作成"に詳細が書かれています......が、このマニュアルを理解して関数について勉強しようと思うのは無理です(苦笑)。本職のプログラマーでも、このような部分をきちんと読む人は殆どいません。もちろん、私もこの記事を書くまで一度も読んだことがありませんでした。通常は、関数の定義の仕方さえ覚えておけば十分です。引数つきの関数は以下のように定義します;

    
    
     fn CreateBoxXYZ nx ny nz = (
      (ここに処理する内容を書く)
     )
    
    

    fn(functionの略)が、"これから関数を定義します"という宣言をしている部分です。続いて関数の名前、それから必要ならば引数が続きます。 = 以降、"()" で囲われている内容が関数の本体です。実際の関数は以下のようになります;

    
    
     fn CreateBoxXYZ nx ny nz = (
    	for i=0 to (nx-1) do (
    		for j=0 to (ny-1) do (
    			for k=0 to (nz-1) do (
    				x = i * 10
    				y = j * 10
    				z = k * 10
    
    				b = Box pos:[x, y, z] length:1 width:1 height:1
    				xyz = (i as String) + "_" + (j as String) + "_" + (k as String)
    				b.Name = "Box_" + xyz
    
    			)
    		)
     	)
     )
    
    

    この関数を呼ぶ方法は以下の通りです;

    
    
     CreteBoxXYZ 10 10 10
    
    

    ループが今までよりも一段増えていますが、もう何てことないと思います。理解を深めるためにも、ぜひ実際にスクリプトを書いてみてくださいね。

    [[SplitPage]]

    MAXScriptの特殊な部分

    ところで、注意深い方なら気づいたかもしれませんが、for ループで使用する値が (nx-1) のように1つ少なくなっています。これは、MAXScript だけに見られる非常に特殊な仕様で、for ループが終わる条件が、指定した値を越えたら終了となっているためです。試しにプログラムを走らせてみます。

    
    
     for i=0 to 10 do (
     	print i
     )
    
    

    0 から始まって 10 までが表示されます。つまり、ループの中身が 11 回繰り返されています。普通の言語だと、これは 9 までが繰り返されます。例えば Python の場合は;

    
    
     for i in range(10):
     	print i
    
    

    これは、0 から 9 までが表示されます。これが普通のプログラミング言語の挙動です。MAXScript に慣れてしまった後に他の言語に移ると非常に大きな違和感を感じる(そしてバグの要因になる)大元なので、最初から"これは特殊なんだ"ということを肝に命じて覚えておいてください。敢えて MAXScript で同じことしようとすると、以下のように初期値を 1 にすることになります。

    
    
     for i=1 to 10 do (
     	print i
     )
    
    

    これはこれで悪くはないですが、普通のプログラミングスタイルとは言えないのであまりお薦めできません。これに関連して、MAXScript の配列も普通のプログラミング言語とは違う指定の仕方をします。前回の記事を参考にしてみてください。selection の中身を取り出すのに、selection[1]、selection[2]......とインデックスが 1 から始まっています。これも、普通のプログラミング言語なら 0 から始まります。

    
    
    (実行例)
    --maxScript
    arr = #("a",  "b", "c" )
    #("a", "b", "c")
    print arr[1]
    "a"
    "a"
    print arr[0]
    -- ランタイムエラー: array index must be positive number, got: 0
    
    #python
    >>> arr = ["a", "b", "c"]
    >>> print arr[1]
    b
    >>> print arr[0]
    a
    >>>
    
    

    この違いも、細心の注意を払わないと思わぬバグの要因になります。しかも、一見すると間違っていることに気づきにくいタイプのバグなので、ぜひ覚えておいてください。ここではこれ以上のことは言及しませんが、興味のある方は、"ポインタ" "アドレス" "配列" などのキーワードを元に自分で調べてみるか、Twitter などで私をつかまえて飲みに誘ってください :-)

    スクリプトでParticle Flowを操る

    ここまでで、簡単なスクリプトの書き方をひと通り学習することができました。そこで応用として、スクリプトを使った特殊な映像表現の一例をご紹介します。
    ここでご紹介する例は、今まで学習した内容から更に踏みこんで、CGについての知識を織り混ぜたものです。いきなりこんなことができるようになるのは無理だと思いますが、これまでの経験や知識とプログラミング技術を効果的に組み合わせるとこんなこともできるんだという参考にしてください。

    今回作成したのは、オブジェクトの影を Box の並びで表現する仕組みです。言葉では分かりにくいので、実際の映像をご覧になってください。
     

     
    このシステムでは、Particle Flow を使用して、パーティクルの生成と影部分の抽出にスクリプトを使用しています。一見複雑そうに見えますが、MAXScript の言語機能としては、ほとんど今回までで説明したものしか使用していません。
    次回は、このサンプルにどのように作ったのかを解説していきますね。

    TEXT_痴山紘史(JCGS)
    映像制作のためのパイプライン構築をはじめ、技術提供を行なっていくエンジニア集団、「JCGS(日本CGサービス)」の代表取締役......というのは表の顔で、実態は飲み会とCG関連の技術が好きなただのCGオタク。
    個人サイト「PHILO式」