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

第59回 インスタンスをクリックした点で回しながらドラッグする

この記事を読むのに必要な時間:およそ 6 分

マウスボタンを放したら慣性で減速しながら移動する

仕上げは,マウスボタンを放したときの慣性の動きだ。最後にドラッグした向きに,インスタンスを減速しながら移動する。そのためには,マウスボタンが放されたら,DisplayObject.enterFrameイベントからドラッグのリスナー関数(xDrag())を除くと同時に,慣性で移動するリスナー関数に差替えることになる。前掲フレームアクションスクリプト1に加える処理は以下のようになる。全体はスクリプト2として後に掲げた。

慣性で移動するリスナー関数(xThrow())は,ドラッグで回すアニメーションのリスナー関数(xDrag())を少し手直しすればよい。第1に,もはや回転の加速はない。次第に回り方は遅くなる。逆に第2として,ドラッグはなくなっても,慣性でインスタンスが移動する。直前のマウスドラッグを慣性のベクトル(velocity)として保持し,その方向に動き続ける。ただし,回転と同じ減速率(deceleration)を乗じるので,やがて止まる。

var velocity:Point;

function xMouseUp(eventObject:MouseEvent):void {
  // ...[中略]...
  addEventListener(Event.ENTER_FRAME, xThrow);
}

function xDrag(eventObject:Event):void {
  // ...[中略]...
  velocity = force;
}

function xThrow(eventObject:Event):void {
  var myMatrix:Matrix = transform.matrix;
  myMatrix.translate(-x, -y);
  myMatrix.rotate(angularVelocity);
  myMatrix.translate(x + velocity.x, y + velocity.y);
  transform.matrix = myMatrix;
  velocity.normalize(velocity.length * deceleration);
  angularVelocity *= deceleration;
  if (Math.abs(angularVelocity) < 0.1 && velocity.length < 0.1) {
    removeEventListener(Event.ENTER_FRAME, xThrow);
  }
}

少し細かい処理を補っておこう。慣性で動くとき,ドラッグはされていないので,回転の中心はインスタンスの基準点にした。そのため,回す前に移動するMatrix.translate()メソッドにはインスタンスの座標をマイナスにした値(-x, -y)が渡されている。そして,回転の後の移動は,慣性のベクトル(velocity)(x, y)座標を加えた。

慣性のベクトル(velocity)の減速は,Point.normalize()メソッドで長さを縮めた。つまり引数には,もとのベクトルの長さPoint.lengthプロパティ)に減速率を乗じた値を渡している。リスナー関数の締めは,回転角(angularVelocity)と慣性ベクトル(velocity)の長さが十分小さくなったことを確かめたうえで,イベントから削除する。これでインスタンスのアニメーションが完全に終わる。でき上がったフレームアクション全体は,つぎのスクリプト2のとおりだ。

スクリプト2 ドラッグで回転してボタンを放すと慣性で移動する

// フレームアクション: マウスでドラッグして回すMovieClipシンボル
var lastMouse:Point;
var angularVelocity:Number = 0;
var velocity:Point;
var deceleration:Number = 0.8;
var ratio:Number = 5 / width / height;
addEventListener(MouseEvent.MOUSE_DOWN, xMouseDown);
function xMouseDown(eventObject:MouseEvent):void {
  lastMouse = new Point(parent.mouseX, parent.mouseY);
  addEventListener(Event.ENTER_FRAME, xDrag);
  stage.addEventListener(MouseEvent.MOUSE_UP, xMouseUp);
}
function xMouseUp(eventObject:MouseEvent):void {
  removeEventListener(Event.ENTER_FRAME, xDrag);
  stage.removeEventListener(MouseEvent.MOUSE_UP, xMouseUp);
  addEventListener(Event.ENTER_FRAME, xThrow);
}
function xDrag(eventObject:Event):void {
  var currentMouse:Point = new Point(parent.mouseX, parent.mouseY);
  var myMatrix:Matrix = transform.matrix;
  var position:Point = new Point(x, y);
  var radius:Point = lastMouse.subtract(position);
  var force:Point = currentMouse.subtract(lastMouse);
  var moment:Number = crossProduct2D(radius, force);
  angularVelocity += moment * ratio;
  myMatrix.translate(-lastMouse.x, -lastMouse.y);
  myMatrix.rotate(angularVelocity);
  myMatrix.translate(currentMouse.x, currentMouse.y);
  transform.matrix = myMatrix;
  lastMouse = currentMouse.clone();
  angularVelocity *= deceleration;
  velocity = force;
}
function xThrow(eventObject:Event):void {
  var myMatrix:Matrix = transform.matrix;
  myMatrix.translate(-x, -y);
  myMatrix.rotate(angularVelocity);
  myMatrix.translate(x + velocity.x, y + velocity.y);
  transform.matrix = myMatrix;
  velocity.normalize(velocity.length * deceleration);
  angularVelocity *= deceleration;
  if (Math.abs(angularVelocity) < 0.1 && velocity.length < 0.1) {
    removeEventListener(Event.ENTER_FRAME, xThrow);
  }
}
function crossProduct2D(point0:Point, point1:Point):Number {
  var vector0:Vector3D = new Vector3D(point0.x, point0.y, 0);
  var vector1:Vector3D = new Vector3D(point1.x, point1.y, 0);
  var crossProduct3D:Vector3D = vector0.crossProduct(vector1);
  return crossProduct3D.z;
}

回転の加速と減速の度合いは変数(ratioとdeceleration)に定めたので,希望の動きになるよう調整してほしい。外積を用いた回転の加速度の求め方(関数crossProduct2D())について,原理はあまり説明しなかった。このお題の結びとして,次項に物理の解説を加える(苦手な読者は読み飛ばしてもらって構わない⁠⁠。

剛体を回す力の働き - 力のモーメント

かたちの変わらない固いもの(⁠⁠剛体」という)を回転させるとき,その力のかかり具合は力のモーメントという考え方で表される。柄の細いドライバーと太いドライバーとでは,同じ力を加えてもねじの回しやすさが変わる。その回しやすさが,力と区別された力のモーメントなのだ。

太いドライバに力が加えやすいのは,てこの原理が働くからだ図3⁠。インスタンスを回転する場合でいえば,重心(中心)が支点でドラッグするマウスポインタの位置が力点になる。その2点を結ぶペクトルに対して垂直に加えた力(動き)が,インスタンスを回すことになる。

図3 てこと力を加える向き

図3 てこと力を加える向き

力がてこ(支点から力点を結ぶベクトル)に対して垂直でない場合,その力のベクトルを対角線とする平行四辺形(長方形を含む)を描けば,その2辺でふたつのベクトルに分けることができる図4⁠。てこに加えられた力(F)も,てこに平行(F//と垂直(Fのふたつのベクトルに分けられる。

図4 てこに加えられた力はてこに平行と垂直なふたつのベクトルに分けられる

図4 てこに加えられた力はてこに平行と垂直なふたつのベクトルに分けられる

インスタンスを回転する働きは,その垂直の力の大きさ(|F|sinθ)に比例する。そして,てこはもちろんその長さ(|r|)に応じて,力がかかりやすくなる。つまり,これらふたつの大きさの積(|r||F|sinθ)に比例して,回転の加速度が増すということだ。この積は,ふたつのベクトル(rとF)を2辺とする平行四辺形の面積に等しい。

ここで,ベクトルの外積を思い出そう(第52回ベクトルの外積で回転の軸を定める参照)。ふたつのベクトルの外積(A×B)の大きさが,まさにふたつのベクトルを2辺とする平行四辺形の面積(|A||B|sinθ)で定められていた第52回表1再掲⁠⁠。

第52回表1 外積A×Bで定められるベクトル(再掲)

外積の要素求められた外積のベクトルとふたつのベクトルAとBとの関係
方向角度 ふたつのベクトルAとBのどちらにも垂直画像
向きベクトルAからBに向かう回転で右ネジの進む向き
大きさ|A||B|sinθ画像

外積はふたつのベクトルのどちらにも垂直なベクトルだった。つまり,xy平面上のふたつのベクトルの外積は,z軸に平行なベクトルになる。それは,外積のベクトルのxy座標が,つねに0であることを意味する。そのため,前掲スクリプト2で外積を用いた関数(crossProduct2D())では,外積のベクトル(crossProduct3D)のz座標値だけ取出して返している。

もっとも,関数の返すz座標値は,引数に受取ったふたつのベクトル(point0とpoint1)が右ネジの位置にあるかそうでないかで,正負は逆になる。すると,インスタンスを回す角度の向きが変わるのである。このようにして外積を使うことにより,マウスでドラッグするインスタンスの位置と向き,および大きさから,回転する加速度とその方向が導けるのだ※1⁠。

Flash Professional CS6が発表された。そこでこの本編のつぎ(第61回)からは,Flash Player 11に備わったStage3Dにもとづく2次元フレームワークStarlingを扱う。

※1
さらに詳しくは,FumioNonaka.com力のモーメントをお読みいただきたい。

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

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書