ActionScript 3.0で始めるオブジェクト指向スクリプティング

第34回3次元空間における回転

今回は、3次元空間でインスタンスを回してみたい。インスタンスを3次元空間で操作するやり方はいくつかある。手軽なものから順に試していこう。

インスタンスを3次元空間のプロパティで操作する

2次元平面でインスタンスを回そうとするとき、手っとり早いのはDisplayObject.rotationプロパティを使うことだ。3次元空間の操作についても、これに相当するプロパティがある。DisplayObject.rotationX/DisplayObject.rotationY/DisplayObject.rotationZプロパティだ。それぞれxyzの各軸回りの角度を度数値で示す。2次元と3次元の違いはあるものの、プロパティの扱い方は変わらない。実際、DisplayObject.rotationプロパティは、3次元で考えればz軸を中心にした回転なので、DisplayObject.rotationZプロパティと同じ値だ。

それでは、DisplayObject.rotationYプロパティを使って、タイムラインに置いたインスタンスmy_mcを、マウスポインタの水平位置に応じてy軸で回転させてみよう。以下のフレームアクションは、マウスポインタとインスタンスとの水平座標の差に応じて回転角を計算し、その値をDisplayObject.rotationYプロパティに設定することにより、インスタンスを水平に回転させる図1⁠。なお、インスタンスの基準点は中心にしてある。

// タイムライン: メイン
// タイムラインにMovieClipインスタンスmy_mcを配置
var nX:Number = my_mc.x;
var nDeceleration:Number = 0.3;
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  my_mc.rotationY += (mouseX - nX) * nDeceleration;
  trace(my_mc.rotationY);   // 確認用
}
図1 インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転
図1 インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転

とても簡単だ。だが、ひとつ気になることがある。確認用に加えたtrace()関数は、回転したインスタンスのDisplayObject.rotationYプロパティの度数値を[出力]する。放っておくと、その数値の桁数はどこまででも増えていく図2⁠。

図2 DisplayObject.rotationYプロパティの値の範囲に制限がない
図2 DisplayObject.rotationYプロパティの値の範囲に制限がない

[ヘルプ]のDisplayObject.rotationYプロパティの項を見ると、値は±180の範囲に調整されるように説明されている。確かに、2次元平面のDisplayObject.rotationプロパティは、そのように角度が換算される。ところが、3次元空間のプロパティDisplayObject.rotationX/DisplayObject.rotationY/DisplayObject.rotationZについては、実際には何の調整もされない。

もちろん、どれだけ桁数が増えてもプロパティが正しくその値を扱えるなら構わない。しかし、内部的に扱える値の範囲は決まっているはずであり、それを超えれば意図しない結果が生じ得る。仕方がないので、自力でその範囲を定めるようにしよう。

角度の値を±180の範囲に定める

角度の値は[ヘルプ]に書かれていた±180に納めることにしよう。実は、角度を0から360までの値に換算する方法はすでに学んだ(第16回三角関数を使った楕円軌道のアニメーション⁠円や楕円軌道のアニメーション⁠⁠。角度が変数nDegreeに納められていたとすると、つぎの式で0以上360未満の値に換えられた。

nDegree = (nDegree % 360 + 360)% 360

では、値の範囲を±180にするにはどうするか[1]⁠。あらかじめ角度に180を足したうえで、上の式により0から360までの値に換算し、その後180差引けばよい。値を±180の範囲に換算する演算は、別に関数として定めよう。その関数xGetDegrees()を加えたのが、つぎのスクリプト1だ。

スクリプト1 インスタンスの基準点からのマウスポインタの水平位置に応じてインスタンスを水平回転
// タイムライン: メイン
// タイムラインにMovieClipインスタンスmy_mcを配置
var nX:Number = my_mc.x;
var nDeceleration:Number = 0.3;
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = my_mc.rotationY + (mouseX - nX) * nDeceleration;
  my_mc.rotationY = xGetDegrees(nRotationY);
  // trace(my_mc.rotationY); // 確認用
}
function xGetDegrees(nDegrees:Number):Number {
  nDegrees += 180;
  nDegrees %= 360;
  nDegrees += 360;
  nDegrees %= 360;
  nDegrees -= 180;
  return nDegrees;
}

動きは前掲のフレームアクションと変わりはない。ただ、コメントアウトしてある確認用のtrace()関数のステートメントを有効にしてみれば、DisplayObject.rotationYプロパティの値が±180の範囲に収まっていることが確かめられる図3⁠。なお、関数xGetDegrees()の中のステートメント行数が、やけに多いように感じるかもしれない。ただ、これは値を0から360までの範囲に換算する前掲式も、細かくステートメントに分けたに過ぎない。

図3 DisplayObject.rotationYプロパティの値が±180の範囲に収まっている
図3 DisplayObject.rotationYプロパティの値が±180の範囲に収まっている 図3 DisplayObject.rotationYプロパティの値が±180の範囲に収まっている

ビットマップのインスタンスを動的に配置する

つぎに、回すインスタンスを予めタイムラインに置かず、動的につくることにする。そのためには、[ライブラリ]のビットマップに[クラス]を設定しておく必要がある。[ライブラリ]でビットマップを選んだら、[ビットマッププロパティ]ダイアログボックスを開く(オプションポップアップまたはショートカットメニューから[プロパティ]もしくは[ライブラリ]のプロパティボタン⁠⁠。[ActionScript用に書き出し]をチェックしたら、[クラス]を入力する。クラス名は「Image0」としよう図4上図⁠。

図4 [ビットマッププロパティ]ダイアログボックスで[クラス]を設定
図4 [ビットマッププロパティ]ダイアログボックスで[クラス]を設定 図4 [ビットマッププロパティ]ダイアログボックスで[クラス]を設定

今回、Image0というクラスは定義していない。また、そのつもりもない。すると、定義されていないクラスを「自動生成」する旨の警告が表れる図4下図⁠。[OK]ボタンをクリックすれば、その名前で空のクラスが自動的につくられるという仕組みだ。そのクラスは空ではあっても、[ビットマッププロパティ]ダイアログボックスに[基本クラス]としてデフォルト入力されているBitmapDataクラスを継承する。BitmapDataはビットマップを扱うクラスなので、そのためのプロパティやメソッドが使える。

ひとつ注意しなければならないのは、BitmapDataがDisplayObjectクラスを継承しないことだ図5⁠。DisplayObjectまたはそのサブクラスのインスタンスでなければ、インスタンスをタイムラインに加えて表示するDisplayObjectContainer.addChild()メソッドの引数として渡せない。つまり、BitmapData(およびそのサブクラスの)インスタンスは、そのままではタイムラインに表示できない。

図5 BitmapDataはDisplayObjectクラスを継承しない
図5 BitmapDataはDisplayObjectクラスを継承しない

そこで、DisplayObjectクラスを継承するBitmapクラスのコンストラクタメソッドに、ビットマップの(BitmapData)インスタンスを引数として渡す。そうすると、Bitmapインスタンスは、引数に受取ったBitmapDataインスタンスのビットマップイメージをもつ。このBitmapインスタンスをDisplayObjectContainer.addChild()メソッドの引数にして、ビットマップイメージをタイムラインに置けばよい図6⁠。

var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
addChild(myBitmap);
図6 ビットマップのイメージをもったBitmapインスタンスがタイムラインに配置
図6 ビットマップのイメージをもったBitmapインスタンスがタイムラインに配置

このフレームアクションでひとつ注目してほしいのは、ビットマップに設定したクラス(Image0)のコンストラクタにふたつの0を引数として渡していることだ。これはスーパークラスBitmapDataのコンストラクタが、インスタンスの幅と高さを必須の引数としていることに対応する。

もっとも、サブクラス(Image0)から渡す引数はともに0でよく、インスタンスを生成すれば[ライブラリ]に納めたビットマップの幅と高さが自動的に設定される。だとすると、わざわざ引数を求める意味がない。そのような指摘を反映して、Flash Professional CS5では、これらふたつの引数は省くことができるようになった[2]⁠。

前掲スクリプト1に修正を加えよう。[ライブラリ]のビットマップイメージを動的に配置して、インスタンス中心からマウスポインタまでの水平位置に応じてビットマップイメージを水平に回すフレームアクションがつぎのスクリプト2だ。

スクリプト2 動的に配置したビットマップイメージをマウスポインタの水平位置に応じて水平回転
// タイムライン: メイン
// [ライブラリ]のビットマップに[クラス]としてImage0を設定
var mySprite:Sprite = new Sprite();   // Spriteインスタンスを生成
var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
var nX:Number = stage.stageWidth / 2;
var nDeceleration:Number = 0.3;
mySprite.x = nX;
mySprite.y = stage.stageHeight / 2;
myBitmap.x = -myBitmap.width / 2;   // Bitmapインスタンスの位置合わせ
myBitmap.y = -myBitmap.height / 2;   // 親Spriteインスタンスの基準点が中心にくるように
addChild(mySprite);
mySprite.addChild(myBitmap);   // Bitmapインスタンスは親Spriteインスタンスの入れ子にする
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = mySprite.rotationY + (mouseX - nX) * nDeceleration;
  mySprite.rotationY = xGetDegrees(nRotationY);
}
function xGetDegrees(nDegrees:Number):Number {
  nDegrees += 180;
  nDegrees %= 360;
  nDegrees += 360;
  nDegrees %= 360;
  nDegrees -= 180;
  return nDegrees;
}

最初のステートメントで、Spriteインスタンスを生成していることに気づくだろう。ビットマップイメージをもったBitmapインスタンスは、そのSpriteインスタンスに入れ子にした。なぜなら、Bitmapインスタンスの基準点が、左上隅にあって動かせないためだ。つまり、Bitmapインスタンスを直接水平に回せば、左端が軸になってしまう。そこで、親(Sprite)インスタンスに入れ子にしたうえで、親の基準点が中心にくるようにBitmapインスタンスの位置を整えている[3]⁠。

これで、[ライブラリ]で[クラス](Image0)を設定したビットマップのイメージが、SpriteとBitmapのインスタンスの入れ子となって配置され、イメージの中心からマウスポインタの水平位置に応じて水平に回転する前掲図1参照⁠。3次元空間における回転は、さらに次回も引続きお題にする。

今回解説した次のサンプルファイルがダウンロードできます。

おすすめ記事

記事・ニュース一覧